﻿using System;
using System.Collections.Generic;
using System.Linq;
using MPI;

namespace Dwarf.Managed
{
    /// <summary>Solve the Pattern Search Problem by FSM with MPI parallelization</summary>
    ///
    class Solver
    {                                                       // MPI variables:
        MPI.Environment mpienv;                             // Enviroment
        MPI.Intracommunicator incomm;                       // Intracommunicator
        int anyTag = Communicator.anyTag;                   // Tag for sending/receiving
        int rank;                                           // rank of the process
        int commsize;                                       // count of processes
        int root = 0;                                       // index of root process. By consideration, equal to 0

        private Configurator dwarfConfigurator;             // processed settings from command line
        private string pattern = null;                      // pattern to be found
        private int[,] states = null;                       // matrix for machine states storing
        private int stateDimensionCoincidence;              // x-dimension of the states matrix (all chars in the pattern text)
        private int stateDimensionChars = 65534;            // y-dimension of the states matrix (all possible chars)
        private List<int> positions;                        // search result
        private int iterCount = 0;

        /// <summary>Construct the new solver</summary>
        /// <param name="configurator">Processed command line arguments</param>
        /// <param name="args">Command line arguments for MPI Environment</param>
        /// 
        public Solver(ref Configurator dwarfConfigurator, string[] args)
        {
            // MPI Initialization
            mpienv = new MPI.Environment(ref args);      // MPI Initialization
            rank = MPI.Communicator.world.Rank;          // Rank of the processors
            commsize = MPI.Communicator.world.Size;      // Count of processes
            incomm = MPI.Intracommunicator.world;

            positions = new List<int>();                 // new list of founded positions

            if (rank == root)
            {
                this.dwarfConfigurator = dwarfConfigurator;
                dwarfConfigurator.WriteSettings();              // write settings to console and profile file
                int contentPartCount = dwarfConfigurator.GetData(ref pattern, commsize);
                iterCount = contentPartCount / commsize; 

                stateDimensionCoincidence = pattern.Length;     // length of pattern string
                states = new int[stateDimensionChars, stateDimensionCoincidence + 1];   // init states array
                FiniteStateMachineBaseMethod.ConstructStates(ref states, pattern);                              
            }
            else
            {
               states = new int[stateDimensionChars, stateDimensionCoincidence + 1];   // init states array
            }
            incomm.Broadcast<string>(ref pattern, root);
            incomm.Broadcast<int>(ref iterCount, root);
            incomm.Broadcast<int>(ref stateDimensionCoincidence, root);     // broadcast the stateDimensionCoincidence
            incomm.Broadcast<int[,]>(ref states, root);                     // broadcast the states array
        }

        /// <summary>Union of two lists</summary>
        /// <param name="listFirst">First List for Union</param>
        /// <param name="listSecond">Second List for Union</param>
        /// <returns>Union of two lists</returns>
        /// 
        private List<int> ListUnion(List<int> listFirst, List<int> listSecond)
        {
            listFirst.AddRange(listSecond);
            return listFirst;
        }

        /// <summary>MPI-parallelized method Pattern Search Problem by FSM method</summary>
        ///
        public void Solve()
        {
            int previousContentPosition = 0;
            for (int i = 0; i < iterCount; i++)
            {
                string contentPart = null;
                if (rank == root)
                {
                    for (int proc = 1; proc < commsize; proc++)
                    {
                        contentPart = dwarfConfigurator.GetNextInputPart(); // get next part from input file for sending to non-root processors
                        incomm.Send(previousContentPosition, proc, root);
                        incomm.Send(contentPart, proc, root);   // send to 'proc' processor the input part
                                                                            // send the content by string object, because of problems with char[] sending
                        previousContentPosition += contentPart.Length - contentPart[contentPart.Length - 1] - 1;
                    }
                    contentPart = dwarfConfigurator.GetNextInputPart();     // get next part from input file for root processor
                }
                else
                {          
                    string content;
                    incomm.Receive(root, anyTag, out previousContentPosition);
                    incomm.Receive(root, anyTag, out content);
                    contentPart = content;
                }
                int contentPartLength = contentPart.Length - contentPart[contentPart.Length - 1] - 1;
                FiniteStateMachineBaseMethod fsmMethod = new FiniteStateMachineBaseMethod(pattern, states, previousContentPosition, contentPart);
                fsmMethod.SetContentLength(contentPartLength);
                fsmMethod.Solve();                                          // Call solver for each content string part
                positions.AddRange(fsmMethod.GetPositionsList());
                if (rank == root)
                {
                    previousContentPosition += contentPartLength;
                }
            }

            var reduceOperation = new ReductionOperation<List<int>>(ListUnion);      // custom operation for all processors result reduce
            List<int> iterPositions = incomm.Reduce<List<int>>(positions, reduceOperation, root);   // mpi-reduce
            positions.Sort();
        }

        /// <summary>Problem results output</summary>
        /// <param name="time">Spent time</param>
        ///
        public void Finish(double time)
        {
            if (rank == root)
            {
                dwarfConfigurator.Close(time, positions);
                Console.WriteLine("Total count of pattern entries: " + positions.Count);           // write all the positions count (all positions will be written into the output file) 
            }
            mpienv.Dispose();
        }
    }
}
